/* JAI-Ext - OpenSource Java Advanced Image Extensions Library
*    http://www.geo-solutions.it/
*    Copyright 2014 GeoSolutions


* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at

* http://www.apache.org/licenses/LICENSE-2.0

* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eclipse.imagen.media.convolve;

import java.awt.RenderingHints;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import org.eclipse.imagen.BorderExtender;
import org.eclipse.imagen.ImageLayout;
import org.eclipse.imagen.KernelJAI;
import org.eclipse.imagen.ROI;
import org.eclipse.imagen.RasterAccessor;
import org.eclipse.imagen.iterator.RandomIter;
import org.eclipse.imagen.media.range.Range;
import org.eclipse.imagen.media.util.ImageUtil;

public class SeparableConvolveOpImage extends ConvolveOpImage {

    private float hValues[];
    private float vValues[];
    private float hTables[][];

    public SeparableConvolveOpImage(
            RenderedImage source,
            BorderExtender extender,
            RenderingHints hints,
            ImageLayout l,
            KernelJAI kernel,
            ROI roi,
            Range noData,
            double destinationNoData,
            boolean skipNoData) {

        super(
                source,
                extender,
                hints,
                l,
                kernel,
                roi,
                noData,
                destinationNoData,
                skipNoData); // TODO Auto-generated constructor stub

        this.kernel = kernel;
        kw = kernel.getWidth();
        kh = kernel.getHeight();
        kx = kernel.getXOrigin();
        ky = kernel.getYOrigin();
        hValues = kernel.getHorizontalKernelData();
        vValues = kernel.getVerticalKernelData();

        if (sampleModel.getDataType() == DataBuffer.TYPE_BYTE) {
            hTables = new float[hValues.length][256];
            for (int i = 0; i < hValues.length; i++) {
                float k = hValues[i];
                for (int j = 0; j < 256; j++) {
                    byte b = (byte) j;
                    float f = (float) j;
                    hTables[i][b + 128] = hasNoData && noData.contains(b) ? 0 : k * f;
                }
            }
        }
    }

    @Override
    protected void byteLoop(RasterAccessor src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {
        int dwidth = dst.getWidth();
        int dheight = dst.getHeight();
        int dnumBands = dst.getNumBands();

        byte dstDataArrays[][] = dst.getByteDataArrays();
        int dstBandOffsets[] = dst.getBandOffsets();
        int dstPixelStride = dst.getPixelStride();
        int dstScanlineStride = dst.getScanlineStride();

        byte srcDataArrays[][] = src.getByteDataArrays();
        int srcBandOffsets[] = src.getBandOffsets();
        int srcPixelStride = src.getPixelStride();
        int srcScanlineStride = src.getScanlineStride();

        float tmpBuffer[] = new float[kh * dwidth];
        int tmpBufferSize = kh * dwidth;

        // X,Y positions
        int x0 = 0;
        int y0 = 0;
        int srcX = src.getX();
        int srcY = src.getY();

        if (caseA || (hasROI && hasNoData && roiContainsTile)) {
            for (int k = 0; k < dnumBands; k++) {
                byte dstData[] = dstDataArrays[k];
                byte srcData[] = srcDataArrays[k];
                int srcScanlineOffset = srcBandOffsets[k];
                int dstScanlineOffset = dstBandOffsets[k];

                int revolver = 0;
                int kvRevolver = 0; // to match kernel vValues
                for (int j = 0; j < kh - 1; j++) {
                    int srcPixelOffset = srcScanlineOffset;

                    for (int i = 0; i < dwidth; i++) {
                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;
                        srcPixelOffset += srcPixelStride;
                    }
                    revolver += dwidth;
                    srcScanlineOffset += srcScanlineStride;
                }

                // srcScanlineStride already bumped by
                // kh-1*scanlineStride
                for (int j = 0; j < dheight; j++) {
                    int srcPixelOffset = srcScanlineOffset;
                    int dstPixelOffset = dstScanlineOffset;

                    for (int i = 0; i < dwidth; i++) {
                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;

                        f = 0.5f;
                        // int a = 0;
                        // The vertical kernel must revolve as well
                        int b = kvRevolver + i;
                        for (int a = 0; a < kh; a++) {
                            f += tmpBuffer[b] * vValues[a];
                            b += dwidth;
                            if (b >= tmpBufferSize) b -= tmpBufferSize;
                        }

                        dstData[dstPixelOffset] = ImageUtil.clampRoundByte(f);
                        srcPixelOffset += srcPixelStride;
                        dstPixelOffset += dstPixelStride;
                    }

                    revolver += dwidth;
                    if (revolver == tmpBufferSize) {
                        revolver = 0;
                    }
                    kvRevolver += dwidth;
                    if (kvRevolver == tmpBufferSize) {
                        kvRevolver = 0;
                    }
                    srcScanlineOffset += srcScanlineStride;
                    dstScanlineOffset += dstScanlineStride;
                }
            }
        } else if (caseB) {
            for (int k = 0; k < dnumBands; k++) {
                byte dstData[] = dstDataArrays[k];
                byte srcData[] = srcDataArrays[k];
                int srcScanlineOffset = srcBandOffsets[k];
                int dstScanlineOffset = dstBandOffsets[k];

                int revolver = 0;
                int kvRevolver = 0; // to match kernel vValues
                for (int j = 0; j < kh - 1; j++) {
                    int srcPixelOffset = srcScanlineOffset;

                    for (int i = 0; i < dwidth; i++) {
                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;
                        srcPixelOffset += srcPixelStride;
                    }
                    revolver += dwidth;
                    srcScanlineOffset += srcScanlineStride;
                }

                // srcScanlineStride already bumped by
                // kh-1*scanlineStride
                for (int j = 0; j < dheight; j++) {
                    int srcPixelOffset = srcScanlineOffset;
                    int dstPixelOffset = dstScanlineOffset;

                    y0 = srcY + j;

                    for (int i = 0; i < dwidth; i++) {

                        x0 = srcX + i;

                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;

                        if (roiBounds.contains(x0, y0) && roiIter.getSample(x0, y0, 0) > 0) {
                            f = 0.5f;
                            // int a = 0;
                            // The vertical kernel must revolve as well
                            int b = kvRevolver + i;
                            for (int a = 0; a < kh; a++) {
                                f += tmpBuffer[b] * vValues[a];
                                b += dwidth;
                                if (b >= tmpBufferSize) b -= tmpBufferSize;
                            }

                            dstData[dstPixelOffset] = ImageUtil.clampRoundByte(f);
                        } else {
                            dstData[dstPixelOffset] = destNoDataByte;
                        }

                        srcPixelOffset += srcPixelStride;
                        dstPixelOffset += dstPixelStride;
                    }

                    revolver += dwidth;
                    if (revolver == tmpBufferSize) {
                        revolver = 0;
                    }
                    kvRevolver += dwidth;
                    if (kvRevolver == tmpBufferSize) {
                        kvRevolver = 0;
                    }
                    srcScanlineOffset += srcScanlineStride;
                    dstScanlineOffset += dstScanlineStride;
                }
            }
        } else if (caseC || (hasROI && hasNoData && roiContainsTile)) {
            for (int k = 0; k < dnumBands; k++) {
                byte dstData[] = dstDataArrays[k];
                byte srcData[] = srcDataArrays[k];
                int srcScanlineOffset = srcBandOffsets[k];
                int dstScanlineOffset = dstBandOffsets[k];

                int revolver = 0;
                int kvRevolver = 0; // to match kernel vValues
                for (int j = 0; j < kh - 1; j++) {
                    int srcPixelOffset = srcScanlineOffset;

                    for (int i = 0; i < dwidth; i++) {
                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;
                        srcPixelOffset += srcPixelStride;
                    }
                    revolver += dwidth;
                    srcScanlineOffset += srcScanlineStride;
                }

                // srcScanlineStride already bumped by
                // kh-1*scanlineStride
                for (int j = 0; j < dheight; j++) {
                    int srcPixelOffset = srcScanlineOffset;
                    int dstPixelOffset = dstScanlineOffset;

                    for (int i = 0; i < dwidth; i++) {
                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;

                        boolean isValid = true;
                        if (skipNoData) {
                            int bandOff = srcBandOffsets[k];
                            for (int kj = 0; kj < kh && isValid; kj++) {
                                int lineOff = (j + kj) * srcScanlineStride + bandOff;
                                for (int ki = 0; ki < kw && isValid; ki++) {
                                    int pixelOff = (i + ki) * srcPixelStride + lineOff;
                                    byte value = srcData[pixelOff];
                                    if (!lut[value + 128]) {
                                        isValid = false;
                                    }
                                }
                            }
                        }

                        if (isValid) {
                            f = 0.5f;
                            // int a = 0;
                            // The vertical kernel must revolve as well
                            int b = kvRevolver + i;
                            for (int a = 0; a < kh; a++) {
                                f += tmpBuffer[b] * vValues[a];
                                b += dwidth;
                                if (b >= tmpBufferSize) b -= tmpBufferSize;
                            }
                            dstData[dstPixelOffset] = ImageUtil.clampRoundByte(f);
                        } else {
                            dstData[dstPixelOffset] = destNoDataByte;
                        }

                        srcPixelOffset += srcPixelStride;
                        dstPixelOffset += dstPixelStride;
                    }

                    revolver += dwidth;
                    if (revolver == tmpBufferSize) {
                        revolver = 0;
                    }
                    kvRevolver += dwidth;
                    if (kvRevolver == tmpBufferSize) {
                        kvRevolver = 0;
                    }
                    srcScanlineOffset += srcScanlineStride;
                    dstScanlineOffset += dstScanlineStride;
                }
            }
        } else {
            for (int k = 0; k < dnumBands; k++) {
                byte dstData[] = dstDataArrays[k];
                byte srcData[] = srcDataArrays[k];
                int srcScanlineOffset = srcBandOffsets[k];
                int dstScanlineOffset = dstBandOffsets[k];

                int revolver = 0;
                int kvRevolver = 0; // to match kernel vValues
                for (int j = 0; j < kh - 1; j++) {
                    int srcPixelOffset = srcScanlineOffset;

                    y0 = srcY + j;

                    for (int i = 0; i < dwidth; i++) {

                        x0 = srcX + i;

                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;
                        srcPixelOffset += srcPixelStride;
                    }
                    revolver += dwidth;
                    srcScanlineOffset += srcScanlineStride;
                }

                // srcScanlineStride already bumped by
                // kh-1*scanlineStride
                for (int j = 0; j < dheight; j++) {
                    int srcPixelOffset = srcScanlineOffset;
                    int dstPixelOffset = dstScanlineOffset;

                    for (int i = 0; i < dwidth; i++) {
                        int imageOffset = srcPixelOffset;
                        float f = 0.0f;
                        for (int v = 0; v < kw; v++) {
                            f += hTables[v][srcData[imageOffset] + 128];
                            imageOffset += srcPixelStride;
                        }
                        tmpBuffer[revolver + i] = f;

                        if (roiBounds.contains(x0, y0) && roiIter.getSample(x0, y0, 0) > 0) {}

                        boolean isValid = true;
                        if (skipNoData) {
                            int bandOff = srcBandOffsets[k];
                            for (int kj = 0; kj < kh && isValid; kj++) {
                                int lineOff = (j + kj) * srcScanlineStride + bandOff;
                                for (int ki = 0; ki < kw && isValid; ki++) {
                                    int pixelOff = (i + ki) * srcPixelStride + lineOff;
                                    byte value = srcData[pixelOff];
                                    if (!lut[value + 128]) {
                                        isValid = false;
                                    }
                                }
                            }
                        }

                        if (isValid) {
                            f = 0.5f;
                            // int a = 0;
                            // The vertical kernel must revolve as well
                            int b = kvRevolver + i;
                            for (int a = 0; a < kh; a++) {
                                f += tmpBuffer[b] * vValues[a];
                                b += dwidth;
                                if (b >= tmpBufferSize) b -= tmpBufferSize;
                            }
                            dstData[dstPixelOffset] = ImageUtil.clampRoundByte(f);
                        } else {
                            dstData[dstPixelOffset] = destNoDataByte;
                        }

                        srcPixelOffset += srcPixelStride;
                        dstPixelOffset += dstPixelStride;
                    }

                    revolver += dwidth;
                    if (revolver == tmpBufferSize) {
                        revolver = 0;
                    }
                    kvRevolver += dwidth;
                    if (kvRevolver == tmpBufferSize) {
                        kvRevolver = 0;
                    }
                    srcScanlineOffset += srcScanlineStride;
                    dstScanlineOffset += dstScanlineStride;
                }
            }
        }
    }

    @Override
    protected void ushortLoop(RasterAccessor src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {

        // TODO Auto-generated method stub

    }

    @Override
    protected void shortLoop(RasterAccessor src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {

        // TODO Auto-generated method stub

    }

    @Override
    protected void intLoop(RasterAccessor src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {

        // TODO Auto-generated method stub

    }

    @Override
    protected void floatLoop(RasterAccessor src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {

        // TODO Auto-generated method stub

    }

    @Override
    protected void doubleLoop(RasterAccessor src, RasterAccessor dst, RandomIter roiIter, boolean roiContainsTile) {

        // TODO Auto-generated method stub

    }
}
